package com.nanhai.buddhist.college.module.study.service.timetable;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.nacos.common.utils.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.nanhai.buddhist.college.framework.common.pojo.CommonResult;
import com.nanhai.buddhist.college.framework.common.util.collection.CollectionUtils;
import com.nanhai.buddhist.college.framework.mybatis.core.dataobject.BaseDO;
import com.nanhai.buddhist.college.module.collegebase.api.collegeclass.CollegeClassApi;
import com.nanhai.buddhist.college.module.collegebase.api.collegeclass.dto.ClassesRespDTO;
import com.nanhai.buddhist.college.module.collegebase.api.room.RoomApi;
import com.nanhai.buddhist.college.module.collegebase.api.room.dto.RoomRespDTO;
import com.nanhai.buddhist.college.module.collegebase.api.student.StudentApi;
import com.nanhai.buddhist.college.module.collegebase.api.student.dto.StudentRespDTO;
import com.nanhai.buddhist.college.module.collegebase.api.teacher.TeacherApi;
import com.nanhai.buddhist.college.module.collegebase.api.teacher.dto.TeacherRespDTO;
import com.nanhai.buddhist.college.module.study.controller.teacher.vo.StudentClockListReqVO;
import com.nanhai.buddhist.college.module.study.controller.teacher.vo.SubjectStudentClockResVO;
import com.nanhai.buddhist.college.module.study.dal.dataobject.qrcodeconfig.QrCodeConfigDO;
import com.nanhai.buddhist.college.module.study.dal.dataobject.studysubject.StudySubjectDO;
import com.nanhai.buddhist.college.module.study.dal.dataobject.studytime.StudyTimeDO;
import com.nanhai.buddhist.college.module.study.dal.mysql.qrcodeconfig.QrCodeConfigMapper;
import com.nanhai.buddhist.college.module.study.dal.mysql.studytime.StudyTimeMapper;
import com.nanhai.buddhist.college.module.study.enums.ClockUpDownStatusEnum;
import com.nanhai.buddhist.college.module.study.enums.StudyStatusEnum;
import com.nanhai.buddhist.college.module.study.service.studysubject.StudySubjectService;
import com.nanhai.buddhist.college.module.study.util.DateConvertUtil;
import jodd.util.CollectionUtil;
import jodd.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import org.springframework.validation.annotation.Validated;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.nanhai.buddhist.college.module.study.controller.admin.timetable.vo.*;
import com.nanhai.buddhist.college.module.study.dal.dataobject.timetable.ClassTimetableDO;
import com.nanhai.buddhist.college.framework.common.pojo.PageResult;

import com.nanhai.buddhist.college.module.study.convert.timetable.ClassTimetableConvert;
import com.nanhai.buddhist.college.module.study.dal.mysql.timetable.ClassTimetableMapper;

import static com.nanhai.buddhist.college.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.nanhai.buddhist.college.framework.common.util.collection.CollectionUtils.convertList;
import static com.nanhai.buddhist.college.framework.common.util.collection.CollectionUtils.convertMap;
import static com.nanhai.buddhist.college.framework.common.util.collection.CollectionUtils.convertSet;
import static com.nanhai.buddhist.college.module.study.enums.ErrorCodeConstants.*;

/**
 * 课程 Service 实现类
 *
 * @author xqenzo@163.com
 */
@Service
@Validated
public class ClassTimetableServiceImpl implements ClassTimetableService {

    @Resource
    private ClassTimetableMapper classTimetableMapper;

    @Resource
    private StudyTimeMapper studyTimeMapper;


    @Resource
    private TeacherApi teacherApi;

    @Resource
    private RoomApi roomApi;

    @Resource
    private StudySubjectService studySubjectService;

    @Resource
    private CollegeClassApi collegeClassApi;

    private static final Integer week = 7;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private QrCodeConfigMapper qrCodeConfigMapper;

    @Resource
    private StudentApi studentApi;


    @Override
    public Long createClassTimetable(ClassTimetableCreateReqVO createReqVO) {
        // 插入
        ClassTimetableDO classTimetable = ClassTimetableConvert.INSTANCE.convert(createReqVO);

        // 创建课程前校验
        ClassTimetableDO classTimetableDO = createCheck(createReqVO);


        LambdaUpdateWrapper<ClassTimetableDO> classTimetableDOLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        classTimetableDOLambdaUpdateWrapper.eq(ClassTimetableDO::getStudyTimeId, createReqVO.getStudyTimeId());
        classTimetableDOLambdaUpdateWrapper.eq(ClassTimetableDO::getWeekId, createReqVO.getWeekId());
        classTimetableDOLambdaUpdateWrapper.eq(ClassTimetableDO::getClassesId, createReqVO.getClassesId());
        classTimetableMapper.update(classTimetable, classTimetableDOLambdaUpdateWrapper);
        // 返回
        return classTimetableDO.getId();
    }

    @NotNull
    private ClassTimetableDO createCheck(ClassTimetableCreateReqVO createReqVO) {
        ClassTimetableDO build = ClassTimetableDO.builder()
                .weekId(createReqVO.getWeekId())
                .classesId(createReqVO.getClassesId())
                .studyTimeId(createReqVO.getStudyTimeId())
                .build();
        ClassTimetableDO classTimetableDO = classTimetableMapper.selectOne(new QueryWrapper<>(build));


        if (Objects.isNull(classTimetableDO)) {
            this.initClassTimeTableByClassId(createReqVO.getClassesId());
            throw exception(CLASS_TIMETABLE_NOT_CREATE);
        }

        // 是否强制修改课程表， 覆盖之前的课程表
        if (!createReqVO.isForceUpdate() && StringUtils.isNotBlank(classTimetableDO.getSubjectId())) {
            throw exception(CLASS_TIMETABLE_EXISTS_COURSE);
        }


        ClassTimetableDO teacherBuild = ClassTimetableDO.builder()
                .weekId(createReqVO.getWeekId())
                .teacherId(createReqVO.getTeacherId())
                .studyTimeId(createReqVO.getStudyTimeId())
                .build();
        ClassTimetableDO teacherTimetableDO = classTimetableMapper.selectOne(new QueryWrapper<>(teacherBuild));

        // 教师存在课程无法创建
        if (ObjectUtil.isNotNull(teacherTimetableDO)) {
            throw exception(TEACHER_TIMETABLE_EXISTS);
        }
        return classTimetableDO;
    }

    @Override
    public void updateClassTimetable(ClassTimetableUpdateReqVO updateReqVO) {
        // 校验存在
        this.validateClassTimetableExists(updateReqVO.getId());
        // 更新
        ClassTimetableDO updateObj = ClassTimetableConvert.INSTANCE.convert(updateReqVO);
        classTimetableMapper.updateById(updateObj);
    }

    @Override
    public void deleteClassTimetable(ClassTimetableDeleteReqVO deleteReqVO) {
        ClassTimetableDO build = ClassTimetableDO.builder()
                .weekId(deleteReqVO.getWeekId())
                .classesId(deleteReqVO.getClassesId())
                .studyTimeId(deleteReqVO.getStudyTimeId())
                .build();
        ClassTimetableDO classTimetableDO = classTimetableMapper.selectOne(new QueryWrapper<>(build));


        //删除莫一节课，将上课的信息 设置为空


        LambdaUpdateWrapper<ClassTimetableDO> classTimetableDOLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        classTimetableDOLambdaUpdateWrapper.set(ClassTimetableDO::getTeacherId, null);
        classTimetableDOLambdaUpdateWrapper.set(ClassTimetableDO::getSubjectId, null);
        classTimetableDOLambdaUpdateWrapper.set(ClassTimetableDO::getRoomId, null);
        classTimetableDOLambdaUpdateWrapper.set(ClassTimetableDO::getRemark, null);
        classTimetableDOLambdaUpdateWrapper.set(ClassTimetableDO::getType, null);
        classTimetableDOLambdaUpdateWrapper.set(ClassTimetableDO::getClassMode, 1);
        classTimetableDOLambdaUpdateWrapper.eq(ClassTimetableDO::getClassesId, deleteReqVO.getClassesId());
        classTimetableDOLambdaUpdateWrapper.eq(ClassTimetableDO::getWeekId, deleteReqVO.getWeekId());
        classTimetableDOLambdaUpdateWrapper.eq(ClassTimetableDO::getStudyTimeId, deleteReqVO.getStudyTimeId());

        classTimetableMapper.update(null, classTimetableDOLambdaUpdateWrapper);

    }

    private void validateClassTimetableExists(Long id) {
        if (classTimetableMapper.selectById(id) == null) {
            throw exception(CLASS_TIMETABLE_NOT_EXISTS);
        }
    }

    @Override
    public ClassTimetableDO getClassTimetable(Long id) {
        return classTimetableMapper.selectById(id);
    }

    @Override
    public List<ClassTimetableDO> getClassTimetableList(Collection<Long> ids) {
        return CollectionUtils.isEmpty(ids) ? Collections.emptyList() : classTimetableMapper.selectBatchIds(ids);
    }

    @Override
    public List<ClassTimetableDO> getClassTimetableListByClassId(String classId) {
        List<ClassTimetableDO> classTimetableDOS = classTimetableMapper.selectList(ClassTimetableDO::getClassesId, classId);
        boolean empty = classTimetableDOS.isEmpty();
        if (empty) {
            this.initClassTimeTableByClassId(classId);
            throw exception(CLASS_TIMETABLE_NOT_CREATE);
        }
        return classTimetableDOS;
    }


    @Override
    public List<ClassTimetableDO> getClassTimetableListByTeacherId(String teacherId) {
        return classTimetableMapper.selectList(ClassTimetableDO::getTeacherId, teacherId);
    }

    @Override
    public PageResult<ClassTimetableDO> getClassTimetablePage(ClassTimetablePageReqVO pageReqVO) {
        return classTimetableMapper.selectPage(pageReqVO);
    }

    @Override
    public List<ClassTimetableDO> getClassTimetableList(ClassTimetableExportReqVO exportReqVO) {
        return classTimetableMapper.selectList(exportReqVO);
    }

    @Override
    public void initClassTimeTableByClassId(String classId) {
        Long timeTableCount = classTimetableMapper.selectCount(ClassTimetableDO::getClassesId, classId);
        if (timeTableCount > 0) {

        } else {
            List<ClassTimetableDO> list = new ArrayList<>();
            List<StudyTimeDO> studyTimeDOS = studyTimeMapper.selectList();
            for (int i = 1; i <= week; i++) {
                int finalI = i;
                List<ClassTimetableDO> collect = studyTimeDOS.stream().map(item ->
                        ClassTimetableDO.builder().classesId(classId).studyTimeId(item.getId().intValue()).weekId(finalI).build()).collect(Collectors.toList());
                list.addAll(collect);
            }
            classTimetableMapper.insertBatch(list);
        }
    }

    @Override
    public List<ClassTimetableRespVO> coverNames(List<ClassTimetableDO> list) {


        // 获取 教师信息
        Set<String> teacherIds = convertSet(list, ClassTimetableDO::getTeacherId);
        Map<String, TeacherRespDTO> stringCollegesRespDTOMap = Collections.EMPTY_MAP;
        if (!teacherIds.isEmpty()) {
            CommonResult<List<TeacherRespDTO>> teacherList = teacherApi.getTeacherByIds(teacherIds);
            stringCollegesRespDTOMap = convertMap(teacherList.getData(), TeacherRespDTO::getId);

        }

        // 获取 课程
        Set<String> subjectIds = convertSet(list, ClassTimetableDO::getSubjectId);
        Map<String, StudySubjectDO> stringStudySubjectDOMap = Collections.EMPTY_MAP;
        ;
        if (!subjectIds.isEmpty()) {
            List<StudySubjectDO> subjectList = studySubjectService.getSubjectList(subjectIds);
            stringStudySubjectDOMap = convertMap(subjectList, StudySubjectDO::getId);

        }

        // 获取 教室信息
        Set<String> roomIds = convertSet(list, ClassTimetableDO::getRoomId);
        Map<String, RoomRespDTO> stringRoomRespDTOMap = Collections.EMPTY_MAP;
        if (!roomIds.isEmpty()) {
            CommonResult<List<RoomRespDTO>> classroomInfoList = roomApi.getClassroomInfoList(roomIds);
            stringRoomRespDTOMap = convertMap(classroomInfoList.getData(), RoomRespDTO::getId);
        }

        // 获取班级信息
        List<String> classIds = convertList(list, ClassTimetableDO::getClassesId);
        CommonResult<List<ClassesRespDTO>> classesList = collegeClassApi.getClassesList(classIds);
        Map<String, ClassesRespDTO> stringClassesRespDTOMap = convertMap(classesList.getData(), ClassesRespDTO::getId);

        List<ClassTimetableRespVO> classTimetableRespVOS = ClassTimetableConvert.INSTANCE.convertList(list);
        Map<String, TeacherRespDTO> finalStringCollegesRespDTOMap = stringCollegesRespDTOMap;
        Map<String, StudySubjectDO> finalStringStudySubjectDOMap = stringStudySubjectDOMap;
        Map<String, RoomRespDTO> finalStringRoomRespDTOMap = stringRoomRespDTOMap;

        // 获取课程时间
        List<Integer> studyTimeIds = convertList(list, ClassTimetableDO::getStudyTimeId);
        final List<StudyTimeDO> studyTimeDOS = studyTimeMapper.selectBatchIds(studyTimeIds);
        Date date = new Date();
        final long time = date.getTime();
        classTimetableRespVOS.forEach(item -> {
            if (StringUtils.isNotBlank(item.getTeacherId())) {
                item.setTeacherName(Optional.ofNullable(finalStringCollegesRespDTOMap).map(s -> s.get(item.getTeacherId())).map(TeacherRespDTO::getTeacherName).orElse(""));
                item.setCollegeName(Optional.ofNullable(finalStringCollegesRespDTOMap).map(s -> s.get(item.getTeacherId())).map(TeacherRespDTO::getCollegeName).orElse(""));
            }
            if (StringUtils.isNotBlank(item.getSubjectId())) {
                item.setSubjectName(Optional.ofNullable(finalStringStudySubjectDOMap).map(s -> s.get(item.getSubjectId())).map(StudySubjectDO::getName).orElse(""));
            }
            if (StringUtils.isNotBlank(item.getRoomId()) && !finalStringRoomRespDTOMap.isEmpty()) {
                item.setRoomName(Optional.ofNullable(finalStringRoomRespDTOMap).map(s -> s.get(item.getRoomId())).map(RoomRespDTO::getClassroomName).orElse(""));
                item.setAddress(Optional.ofNullable(finalStringRoomRespDTOMap).map(s -> s.get(item.getRoomId())).map(RoomRespDTO::getAddress).orElse(""));
            }
            if (StringUtils.isNotBlank(item.getClassesId()) && !stringClassesRespDTOMap.isEmpty()) {
                item.setClassName(Optional.ofNullable(stringClassesRespDTOMap).map(s -> s.get(item.getClassesId())).map(ClassesRespDTO::getClassesName).orElse(""));
            }
            if (CollectionUtils.isNotEmpty(studyTimeDOS)) {
                // 设置课程状态
                List<StudyTimeDO> collect = studyTimeDOS.stream().filter(s -> s.getId().intValue() == item.getStudyTimeId()).collect(Collectors.toList());
                if (CollectionUtils.isNotEmpty(collect)) {
                    StudyTimeDO studyTimeDO = collect.get(0);
                    item.setSubjectBeginTime(studyTimeDO.getBeginTime());
                    item.setSubjectEndTime(studyTimeDO.getEndTime());


                    Date beginTime = DateConvertUtil.setTimeHMS(studyTimeDO.getBeginTime(), date);
                    Date endTime = DateConvertUtil.setTimeHMS(studyTimeDO.getEndTime(), date);
                    if (time < beginTime.getTime()) {
                        item.setSubjectStatus(StudyStatusEnum.NOT_START.getStatus());
                    } else if (time > endTime.getTime()) {
                        item.setSubjectStatus(StudyStatusEnum.END.getStatus());
                    } else {
                        item.setSubjectStatus(StudyStatusEnum.START.getStatus());
                    }
                }
            }


        });

        return classTimetableRespVOS;

    }

    @Override
    public List<ClassTimetableDO> getTodaySubjectByDate(ClassTimetableTodayReqVO reqVO) {
        List<ClassTimetableDO> classTimetableDOS = classTimetableMapper.selectList(new LambdaQueryWrapper<ClassTimetableDO>()
                .eq(ClassTimetableDO::getClassesId, reqVO.getClassesId())
                .isNotNull(ClassTimetableDO::getTeacherId)
                .eq(ClassTimetableDO::getWeekId, reqVO.getWeekId()));
        boolean empty = classTimetableDOS.isEmpty();
        if (empty) {
            this.initClassTimeTableByClassId(reqVO.getClassesId());
            throw exception(CLASS_TIMETABLE_NOT_CREATE);
        }
        return classTimetableDOS;
    }

    @Override
    public List<ClassTimetableDO> getTodayTeacherClassTimetableList(ClassTimetableTeacherTodayVO reqVo) {
        List<ClassTimetableDO> classTimetableDOS = classTimetableMapper.selectList(new LambdaQueryWrapper<ClassTimetableDO>()
                .eq(ClassTimetableDO::getTeacherId, reqVo.getTeacherId()).eq(ClassTimetableDO::getWeekId, reqVo.getWeekId()));
        boolean empty = classTimetableDOS.isEmpty();
        if (empty) {
            throw exception(CLASS_TIMETABLE_NOT_CREATE);
        }
        return classTimetableDOS;
    }

    @Override
    public String getQrCodeStr(Long id) {
        ClassTimetableDO classTimetableDO = classTimetableMapper.selectById(id);
        if (ObjectUtil.isNull(classTimetableDO)) {
            throw exception(CLASS_TIMETABLE_NOT_EXISTS);
        }
        QrCodeConfigDO qrCodeConfigDO = qrCodeConfigMapper.selectOne(new LambdaQueryWrapper<QrCodeConfigDO>().orderByDesc(BaseDO::getCreateTime).last("limit 1"));
        if (ObjectUtil.isNull(qrCodeConfigDO)) {
            throw exception(QR_CODE_CONFIG_NOT_EXISTS);
        }

        // 校验二维码创建时间 并返回 上下课类型
        String tag = checkCreatedTime(classTimetableDO, qrCodeConfigDO);
        long timeout = qrCodeConfigDO.getRefreshNum().longValue();
        // 随机Key
        String redisKey = "Timetable:" + id + UUID.randomUUID().toString() + tag;

        redisTemplate.opsForValue().set(redisKey, classTimetableDO, timeout, TimeUnit.SECONDS);
        return redisKey;
    }



    /**
     * 获取课程时间  结合 二维码配置  判断是否能够生成二维码
     * 返回值 tag  区别上下 二维码
     *
     * @param classTimetableDO
     * @param qrCodeConfigDO
     */
    private String checkCreatedTime(ClassTimetableDO classTimetableDO, QrCodeConfigDO qrCodeConfigDO) {
        String tag = "";
        StudyTimeDO studyTimeDO = studyTimeMapper.selectById(classTimetableDO.getStudyTimeId());
        Date date = new Date();
        long time = date.getTime();
        // 判断是否在 上课二维码的创建时间
        Date beginTime = DateConvertUtil.setTimeHMS(studyTimeDO.getBeginTime(), date);
        final String up = checkTime(qrCodeConfigDO.getStudyTimeAdvance(), qrCodeConfigDO.getStudyTimeStop(), time, beginTime, ClockUpDownStatusEnum.UP.getStatus());

        // 判断是否在 下课二维码的创建时间
        Date endTime = DateConvertUtil.setTimeHMS(studyTimeDO.getEndTime(), date);
        final String down = checkTime(qrCodeConfigDO.getQuittingTimeAdvance(), qrCodeConfigDO.getQuittingTimeStop(), time, endTime, ClockUpDownStatusEnum.DOWN.getStatus());


        if (StringUtil.isNotBlank(up)) {
            tag = up;
        } else if (StringUtil.isNotBlank(down)) {
            tag = down;
        } else {
            throw exception(QRCODE_CREATED_NOT_TIME);
        }

        return tag;
    }

    /**
     * 开始时间截止时间判断
     *
     * @param advance   提前时间
     * @param stop      截止时间
     * @param time      当前时间
     * @param beginTime 操作时间
     */
    private String checkTime(Integer advance, Integer stop, long time, Date beginTime, Integer upDown) {
        //提前
        long studyTimeAdvance = beginTime.getTime() - advance.longValue() * 60 * 1000;
        //截止
        long studyTimeStop = beginTime.getTime() + stop.longValue() * 60 * 1000;

        if (studyTimeAdvance <= time && time <= studyTimeStop) {
            return upDown + "";
        }
        return "";
    }


}
